import { Shell, ShellArgs } from "./Shell";
import { AppInfo, DeviceInfo } from "../types/OhosTypes";

/**
 * DeviceShell
 */
export class DeviceShell {
    private static defaultInstance: DeviceShell = new DeviceShell();
    private static deviceMap: Map<string, DeviceShell> = new Map();
    private deviceInfo?: DeviceInfo;

    constructor(deviceId?: string) {
        this.deviceInfo = new DeviceInfo(deviceId);
    }

    /**
     * 获取DeviceShell实例
     * @param deviceId device id
     * @returns DeviceShell
     */
    public static getDeviceShell(deviceId?: string) : DeviceShell {
        let shellInst: DeviceShell | undefined = DeviceShell.deviceMap.get(deviceId || '');
        if (!shellInst) {
            shellInst = new DeviceShell(deviceId);
            DeviceShell.deviceMap.set(deviceId || '', shellInst);
        }
        return shellInst;
    }

    /**
     * clear device shell instances
     */
    public static clearDeviceShell() : void {
        DeviceShell.deviceMap.clear();
    }

    private getDeviceCmd(): string {
        let deviceId = this.getDeviceId();
        return `${deviceId === '' ? '' : `-t ${deviceId}`}`;
    }

    /**
     * 默认 DeviceShell 单例
     * @returns device instance
     */
    public static default(): DeviceShell {
        return this.defaultInstance;
    }

    /**
     * 获取设备信息
     * @returns device info
     */
    public getDeviceInfo(): DeviceInfo | undefined {
        return this.deviceInfo;
    }

    /**
     * 获取设备ID
     * @returns device id
     */
    public getDeviceId() : string {
        return this.deviceInfo?.deviceId || '';
    }

    /**
     * 执行设备命令
     * @param cmd command
     * @param excludeEmptyLine 输出不包含空行
     * @param trimLine 输出行去空格
     * @returns 输出信息
     */
    public execSync(cmd: string, excludeEmptyLine: boolean = true, trimLine: boolean = true): string[] {
        let args: ShellArgs = { 
            excludeEmptyLine: excludeEmptyLine, 
            trimLine: trimLine }
        return Shell.execSync(`hdc ${this.getDeviceCmd()} shell "${cmd}"`, args);
    }

    /**
     * 接收文件到本地
     * @param remoteFile 远程文件路径
     * @param localFile 本地文件路径
     */
    public recvFile(remoteFile: string, localFile: string): void {
        Shell.execSync(`hdc ${this.getDeviceCmd()} file recv "${remoteFile}" "${localFile}"`);
    }

    /**
     * 发送文件到设备
     * @param localFile 本地文件路径
     * @param remoteFile 远程文件路径
     */
    public sendFile(localFile: string, remoteFile: string): void {
        Shell.execSync(`hdc ${this.getDeviceCmd()} file send "${localFile}" "${remoteFile}"`);
    }

    /**
     * 通过应用名称获取app信息
     * @param bundleName 应用名称
     * @returns AppInfo
     */
    public getAppInfoByName(bundleName: string) : AppInfo { 
        const appInfo = new AppInfo();
        appInfo.name = bundleName;
        appInfo.pid = this.getAppPid(bundleName);
        return appInfo;
    }

    /**
     * 通过pid获取app信息
     * @param pid pid
     * @returns AppInfo
     */
    public getAppInfoByPid(pid: number) : AppInfo {
        const appInfo = new AppInfo();
        appInfo.pid = pid;
        appInfo.name = this.getAppName(pid);
        return appInfo;
    }

    /**
     * 应用进程PID
     * @param bundleName 应用名称
     * @returns pid | undefined
     */
    public getAppPid(bundleName: string) : number | undefined {
        let strs = this.execSync(`pidof ${bundleName}`);
        if (strs.length === 1) {
            return parseInt(strs[0].trim().split(' ')[0]);
        } else {
            return undefined;
        }
    }

    /**
     * 获取应用名称
     * @param pid 进程id
     * @returns 进程名称
     */
    public getAppName(pid: number) : string {
        // 20008         2940   674 0 12:35:30 ?     00:00:16 com.xxx.yyy.zzz
        let strs = this.execSync(`ps -ef | grep ${pid} | grep -v grep`);
        let appName: string | undefined;
        if (strs.length === 1) {
            appName = strs[0].split(/\s+/).pop();
        }
        return appName || '';
    }

    /**
     * 是否是Root镜像设备
     * @returns true/false
     */
    public isRootDevice() : boolean {
        let strs = this.execSync('ls /system');
        return strs.findIndex(str => str.indexOf('ls: /system: Permission denied') >= 0) == -1;
    }

    /**
     * 指定pid GC
     * @param pid 进程id
     */
    public async forceGC(pid: number) : Promise<void> { 
        let cmd = `hidumper --mem-jsheap ${pid} --gc`;
        this.execSync(cmd);
    }

    /**
     * 指定包名 GC
     * @param name 进程名
     */
    public async forceGCByName(app: string) : Promise<void> { 
        let pid = this.getAppPid(app);
        if (pid) {
            await this.forceGC(pid);
        }
    }
}